/**
 * @author Sergey Kolchin <ksa242@gmail.com>
 */

using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Threading;


namespace DeathByCaptcha
{
    public delegate void DecodeDelegate(Captcha captcha);


    abstract public class Client
    {
        public const string Version = "DBC/.NET v4.0.6";
        public const int SoftwareVendorId = 0;

        public const int DefaultTimeout = 60;
        public const int PollsInterval = 5;

        public bool Verbose = false;

        protected string _username = "";
        protected string _password = "";

        protected Object _callLock = new Object();


        public Hashtable Credentials
        {
            get
            {
                Hashtable userpwd = new Hashtable();
                userpwd["username"] = _username;
                userpwd["password"] = _password;
                return userpwd;
            }
        }

        public User User
        {
            get
            {
                return GetUser();
            }
        }

        public double Balance
        {
            get
            {
                return GetBalance();
            }
        }


        protected void Log(string call, string msg)
        {
            if (this.Verbose)
            {
                Console.WriteLine(DateTime.Now.Ticks + " " + call + (null != msg ? ": " + msg : ""));
            }
        }

        protected void Log(string call)
        {
            this.Log(call, null);
        }


        protected byte[] Load(Stream st)
        {
            long pos = -1;
            if (st.CanSeek)
            {
                pos = st.Position;
                st.Position = 0;
            }
            int n = 0, offset = 0, chunk_size = 1024;
            byte[] buf = new byte[chunk_size];
            while (st.CanRead && 0 < (n = st.Read(buf, offset, chunk_size)))
            {
                offset += n;
                Array.Resize(ref buf, offset + chunk_size);
            }
            if (-1 < pos)
            {
                st.Position = pos;
            }
            Array.Resize(ref buf, offset);
            return buf;
        }

        protected byte[] Load(string fn)
        {
            if (!File.Exists(fn))
            {
                throw new FileNotFoundException(
                    "CAPTCHA image file " + fn + " not found"
                );
            }
            else
            {
                using (FileStream st = File.OpenRead(fn))
                {
                    return this.Load(st);
                }
            }
        }


        public Client(string username, string password)
        {
            this._username = username;
            this._password = password;
        }


        abstract public void Close();


        abstract public User GetUser();

        public double GetBalance()
        {
            return this.GetUser().Balance;
        }


        abstract public Captcha GetCaptcha(int id);

        public Captcha GetCaptcha(Captcha captcha)
        {
            return this.GetCaptcha(captcha.Id);
        }

        public string GetText(int id)
        {
            return this.GetCaptcha(id).Text;
        }

        public string GetText(Captcha captcha)
        {
            return this.GetCaptcha(captcha).Text;
        }


        abstract public Captcha Upload(byte[] img);

        public Captcha Upload(Stream st)
        {
            return this.Upload(this.Load(st));
        }

        public Captcha Upload(string fn)
        {
            return this.Upload(this.Load(fn));
        }


        abstract public bool Remove(int id);

        public bool Remove(Captcha captcha)
        {
            return this.Remove(captcha.Id);
        }


        abstract public bool Report(int id);

        public bool Report(Captcha captcha)
        {
            return this.Report(captcha.Id);
        }


        public Captcha Decode(byte[] img, int timeout)
        {
            DateTime deadline =
                DateTime.Now.AddSeconds(0 < timeout ? timeout : Client.DefaultTimeout);
            Captcha captcha = this.Upload(img);
            if (null != captcha)
            {
                while (deadline > DateTime.Now && !captcha.Solved)
                {
                    Thread.Sleep(Client.PollsInterval * 1000);
                    captcha = this.GetCaptcha(captcha);
                }
                if (captcha.Solved && captcha.Correct)
                {
                    return captcha;
                }
            }
            return null;
        }

        protected void DecodeWithCallback(object state)
        {
            DecodePayload payload = (DecodePayload)state;
            payload.Callback(this.Decode(payload.Image, payload.Timeout));
        }


        public void Decode(DecodeDelegate callback, byte[] img, int timeout)
        {
            new Thread(DecodeWithCallback).Start(new DecodePayload()
            {
                Callback = callback,
                Image = img,
                Timeout = timeout,
            });
        }

        public Captcha Decode(byte[] img)
        {
            return this.Decode(img, 0);
        }

        public void Decode(DecodeDelegate callback, byte[] img)
        {
            this.Decode(callback, img, 0);
        }

        public Captcha Decode(Stream st, int timeout)
        {
            return this.Decode(this.Load(st), timeout);
        }

        public void Decode(DecodeDelegate callback, Stream st, int timeout)
        {
            this.Decode(callback, this.Load(st), timeout);
        }

        public Captcha Decode(Stream st)
        {
            return this.Decode(st, 0);
        }

        public void Decode(DecodeDelegate callback, Stream st)
        {
            this.Decode(callback, this.Load(st), 0);
        }

        public Captcha Decode(string fn, int timeout)
        {
            return this.Decode(this.Load(fn), timeout);
        }

        public void Decode(DecodeDelegate callback, string fn, int timeout)
        {
            this.Decode(callback, this.Load(fn), timeout);
        }

        public Captcha Decode(string fn)
        {
            return this.Decode(fn, 0);
        }

        public void Decode(DecodeDelegate callback, string fn)
        {
            this.Decode(callback, this.Load(fn), 0);
        }
    }
}







//using System;
//using System.Collections;
//using System.IO;
//using System.Net;
//using System.Security.Cryptography;
//using System.Text;
//using System.Threading;
//using System.Web;


//namespace DeathByCaptcha
//{
//    public class Client
//    {
//        public const string InvalidCredentialsError = "login.invalid_userpwd";

//        public const string IsBannedError          = "user.is_banned";
//        public const string NoFactoryError         = "user.no_factory_assigned";
//        public const string InsufficientFundsError = "user.insufficient_funds";

//        public const string CaptchaNotFoundError = "captcha.not_found";
//        public const string CaptchaUploadError   = "captcha.upload_failed";
//        public const string InvalidCaptchaError  = "captcha.invalid_data";


//        private ApiCaller caller = new ApiCaller();
//        private Hashtable userpwd = new Hashtable();

//        public bool IsLoggedIn
//        {
//            get
//            {
//                return userpwd.ContainsKey("username") &&
//                       userpwd.ContainsKey("password");
//            }
//            set
//            {
//                throw new AccessViolationException(
//                    "Setting logged in/out flag is not allowed"
//                );
//            }
//        }

//        public Double Balance
//        {
//            get
//            {
//                if (this.IsLoggedIn) {
//                    string key = "balance";
//                    if (this.caller.Call(
//                        "user",
//                        this.GetEncodedCredentials()
//                    ) && caller.Response.ContainsKey(key)) {
//                        try {
//                            return Double.Parse((string)caller.Response[key]);
//                        } catch (Exception) {}
//                    }
//                }
//                return 0.0;
//            }
//            set
//            {
//                throw new AccessViolationException(
//                    "Balance property is read-only"
//                );
//            }
//        }

//        public Hashtable Response
//        {
//            get
//            {
//                return caller.Response;
//            }
//            set
//            {
//                this.caller.Response = value;
//            }
//        }

//        public string Error
//        {
//            get
//            {
//                return caller.Error;
//            }
//            set
//            {
//                this.caller.Error = value;
//            }
//        }


//        public int PollInterval
//        {
//            get
//            {
//                return caller.PollInterval;
//            }
//            set
//            {
//                caller.PollInterval = value;
//            }
//        }

//        public WebProxy Proxy
//        {
//            get
//            {
//                return caller.Proxy;
//            }
//            set
//            {
//                caller.Proxy = value;
//            }
//        }


//        private string GetEncodedCredentials()
//        {
//            return this.IsLoggedIn
//                ? "username=" + HttpUtility.UrlEncode((string)this.userpwd["username"]) +
//                  "&password=" + HttpUtility.UrlEncode((string)this.userpwd["password"]) +
//                  "&is_hashed=1"
//                : "";
//        }


//        public Client()
//        {
//            this.Init();
//        }

//        public Client Init()
//        {
//            this.caller.Init();
//            this.userpwd.Clear();
//            return this;
//        }

//        public bool Login(string username, string password)
//        {
//            this.userpwd.Clear();
//            if (0 < username.Length) {
//                this.userpwd.Add("username", username);
//                if (0 < password.Length) {
//                    SHA1 digest = new SHA1CryptoServiceProvider();
//                    this.userpwd.Add("password", BitConverter.ToString(digest.ComputeHash(
//                        Encoding.UTF8.GetBytes(password)
//                    )).Replace("-", "").ToLower());
//                    digest.Clear();
//                }
//            }
//            return this.IsLoggedIn;
//        }

//        public Client Logout()
//        {
//            this.userpwd.Clear();
//            return this;
//        }

//        public int Upload(byte[] image)
//        {
//            if (!this.IsLoggedIn) {
//                return 0;
//            }

//            string crlf = "\r\n";
//            string boundary = BitConverter.ToString(
//                (new SHA1CryptoServiceProvider()).ComputeHash(
//                    Encoding.UTF8.GetBytes(DateTime.Now.ToString("G"))
//                )
//            );
//            byte[] body = Encoding.ASCII.GetBytes(
//                "--" + boundary + crlf + "Content-Disposition: form-data; name=\"username\"" + crlf + crlf + (string)this.userpwd["username"] + crlf + crlf +
//                "--" + boundary + crlf + "Content-Disposition: form-data; name=\"password\"" + crlf + crlf + (string)this.userpwd["password"] + crlf + crlf +
//                "--" + boundary + crlf + "Content-Disposition: form-data; name=\"is_hashed\"" + crlf + crlf + "1" + crlf + crlf +
//                "--" + boundary + crlf + "Content-Disposition: form-data; name=\"captchafile\"; filename=\"captcha\"" + crlf + "Content-Type: application/octet-stream" + crlf + crlf
//            );

//            int l = body.Length;
//            Array.Resize(ref body, l + image.Length);
//            image.CopyTo(body, l);

//            byte[] footer = Encoding.ASCII.GetBytes(crlf + "--" + boundary + "--" + crlf);
//            l = body.Length;
//            Array.Resize(ref body, l + footer.Length);
//            footer.CopyTo(body, l);

//            if (this.caller.Call(
//                "captcha",
//                body,
//                "multipart/form-data; boundary=" + boundary
//            ) && this.Response.ContainsKey("captcha")) {
//                if (this.Response.ContainsKey("poll_interval")) {
//                    try {
//                        this.PollInterval = Int16.Parse((string)this.Response["poll_interval"]);
//                    } catch (Exception) {}
//                }

//                try {
//                    return Int32.Parse((string)this.Response["captcha"]);
//                } catch (Exception) {}
//            }

//            return 0;
//        }

//        public int Upload(string filename)
//        {
//            if (File.Exists(filename)) {
//                return this.Upload(File.ReadAllBytes(filename));
//            } else {
//                throw new ArgumentException(
//                    "Image file " + filename + " not found"
//                );
//            }
//        }

//        public string GetText(int id, int maxPolls)
//        {
//            string method = "captcha/" + id;

//            int poll = 0;
//            while ((0 == maxPolls) || (poll < maxPolls)) {
//                if (this.Response.ContainsKey("text")) {
//                    string text = (string)this.Response["text"];
//                    if ("" != text) {
//                        return text;
//                    }
//                }

//                Thread.Sleep(this.PollInterval * 1000);
//                poll++;

//                this.caller.Call(method);
//            }

//            return "";
//        }

//        public string GetText(int id)
//        {
//            return this.GetText(id, 0);
//        }

//        public bool Remove(int id)
//        {
//            return this.IsLoggedIn &&
//                   this.caller.Call("captcha/" + id + "/remove", this.GetEncodedCredentials());
//        }

//        public bool Report(int id, bool isCorrect)
//        {
//            return isCorrect ||
//                   (this.IsLoggedIn && this.caller.Call("captcha/" + id + "/report", this.GetEncodedCredentials()));
//        }

//        public bool Report(int id)
//        {
//            return this.Report(id, true);
//        }

//        public string Decode(string filename, int maxPolls)
//        {
//            int id = this.Upload(filename);
//            if (0 < id) {
//                string text = this.GetText(id, maxPolls);
//                if ("" != text) {
//                    return text;
//                } else {
//                    this.Remove(id);
//                }
//            }
//            return "";
//        }

//        public string Decode(string filename)
//        {
//            return this.Decode(filename, 0);
//        }
//    }
//}
